/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.dubbo.rpc.cluster.loadbalance;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcStatus;
import org.apache.dubbo.rpc.support.RpcUtils;

import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

/**
 * LeastActiveLoadBalance
 * <p>
 * Filter the number of invokers with the least number of active calls and count the weights and quantities of these invokers.
 * If there is only one invoker, use the invoker directly;
 * if there are multiple invokers and the weights are not the same, then random according to the total weight;
 * if there are multiple invokers and the same weight, then randomly called.
 *
 * 最少活跃优先 + 加权随机
 */
public class LeastActiveLoadBalance extends AbstractLoadBalance {

    public static final String NAME = "leastactive";

    @Override
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        /**
         * 加权最少活跃调用优先，活跃数越低，越优先调用，相同活跃数的进行加权随机。活跃数指调用前后计数差（针对特定提供者：请求发送数 - 响应返回数），
         * 表示特定提供者的任务堆积量，活跃数越低，代表该提供者处理能力越强。
         * 使慢的提供者收到更少请求，因为越慢的提供者的调用前后计数差会越大；相对的，处理能力越强的节点，处理更多的请求
         */

        // Number of invokers
        int length = invokers.size();

        // The least active value of all invokers


        // 记录最小活跃度
        int leastActive = -1;

        // The number of invokers having the same least active value (leastActive)
        /**
         * 记录活跃度最小的 invoker数量。为什么还会有最小活跃度数量？
         * 因为最小活跃度 invoker 会出现相同的情况
         */
        int leastCount = 0;
        // The index of invokers having the same least active value (leastActive)
        // 用于存放 具有相同最小活越度的 invoker 索引
        int[] leastIndexes = new int[length];
        // the weight of every invokers
        // 提供者的权重
        int[] weights = new int[length];

        // The sum of the warmup weights of all the least active invokers
        // 最小活越度的 invoker 权重总和值
        int totalWeight = 0;

        // The weight of the first least active invoker
        // 第一个最小活越度的 invoker 的权重
        int firstWeight = 0;

        // Every least active invoker has the same weight value?
        // 最小活越度的 invoker 的权重是否相同
        boolean sameWeight = true;


        // Filter out all the least active invokers
        for (int i = 0; i < length; i++) {


            Invoker<T> invoker = invokers.get(i);


            // Get the active number of the invoker
            // 获取该提供者的活跃度
            int active = RpcStatus.getStatus(invoker.getUrl(), RpcUtils.getMethodName(invocation)).getActive();


            // Get the weight of the invoker's configuration. The default value is 100.
            // 获取提供者权重
            int afterWarmup = getWeight(invoker, invocation);


            // save for later use
            // 保存服务提供者权重
            weights[i] = afterWarmup;


            // If it is the first invoker or the active number of the invoker is less than the current least active number

            /**
             * 第一次进来肯定是进到if里面。如果后续的活跃度小于之前的活跃度。那么就会重新设置最小活跃度
             */
            if (leastActive == -1 || active < leastActive) {

                // Reset the active number of the current invoker to the least active number
                // 设置最小的活跃度
                leastActive = active;
                // Reset the number of least active invokers
                //
                leastCount = 1;

                // Put the first least active invoker first in leastIndexes
                //设置存放最小活跃的索引
                leastIndexes[0] = i;
                // Reset totalWeight
                // 设置最小活跃度的权重和值
                totalWeight = afterWarmup;
                // Record the weight the first least active invoker
                // 设置最小活跃的 权重
                firstWeight = afterWarmup;
                // Each invoke has the same weight (only one invoker here)

                sameWeight = true;

                // If current invoker's active value equals with leaseActive, then accumulating.
            } else if (active == leastActive) {
                // 代码执行到这，证明出现了多个相同最小活跃度

                // Record the index of the least active invoker in leastIndexes order
                // 记录 相同最小活跃度的索引值
                leastIndexes[leastCount++] = i;

                // Accumulate the total weight of the least active invoker
                // 最小活跃度的权重和值相加起来
                totalWeight += afterWarmup;

                // If every invoker has the same weight?
                // 如果每个调用者都有相同的权重
                if (sameWeight && afterWarmup != firstWeight) {
                    //设置权重不相同
                    sameWeight = false;
                }
            }
        }
        // Choose an invoker from all the least active invokers


        // 最小活跃 只有一个
        if (leastCount == 1) {
            // If we got exactly one invoker having the least active value, return this invoker directly.
            return invokers.get(leastIndexes[0]);
        }



        //证明有多个最小活跃。判断权重是否不相同
        if (!sameWeight && totalWeight > 0) {

            // 代码执行到这证明 有多个最小活跃并且权重是不相同的。那么就加权随机了
            // If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on
            // totalWeight.
            // 获取随机数
            int offsetWeight = ThreadLocalRandom.current().nextInt(totalWeight);

            /**
             * 案例：
             * 现在有3个最小活跃度invoker：
             *  invoker（权重）： A（3）   B（5）   C（2）
             *  权重和值：        0+3=3   3+5=8   8+2=10
             *  权重范围          0-2     3-7     8-9
             *  活跃的 ：          0     0     0
             *  权重和值 ：totalWeight = 10
             *  随机数：offsetWeight = 6
             *  可以肉眼看到 最终是选择的 invoker是 B
             * for循环 代码逻辑 可以看到：
             *  1. 获取 A的权重和值 3，然后随机数 6 - 3 = 3 不小于 0，那么继续 循环。此时随机数 offsetWeight=3
             *  2. 获取 B 的权重和值 8，然后随机数 3 - 9 = -5 小于0了，正在找到了 invoker B。循环结束。
             *  最终 负载均衡（最少活跃优先 + 加权随机） 算法 返回是 B invoker
             *
             */
            // Return a invoker based on the random value.
            // 遍历最小活跃
            for (int i = 0; i < leastCount; i++) {

                // 获取最小活跃度 索引
                int leastIndex = leastIndexes[i];
                // 最小活跃的权重和值减去随机数
                offsetWeight -= weights[leastIndex];

                // 如果小于0 证明是这个 invoker 胜出
                if (offsetWeight < 0) {
                    // 从 invoker 列表获取 最小活跃的胜出的invoker
                    return invokers.get(leastIndex);
                }
            }
        }

        // If all invokers have the same weight value or totalWeight=0, return evenly.
        // 代码执行到这 证明 有多个最小活跃并且权重是是相同的，那么就随机了
        return invokers.get(leastIndexes[ThreadLocalRandom.current().nextInt(leastCount)]);
    }
}
